#include "stdinc.h"
#include "esp_bt.h"
#include "esp_blufi_api.h"
#include "esp_bt_defs.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_main.h"
#include "esp_bt_device.h"
#include "esp_wifi.h"
#include "blufi.h"

#define LOGTAG "SZW:BLUFI"


// 要上报的信息
typedef struct {
    wifi_mode_t mode;
    uint8_t sta_bssid[6];           /*!< BSSID of station interface */
    bool sta_bssid_set;             /*!< is BSSID of station interface set */
    char sta_ssid[32];           /*!< SSID of station interface */
    char sta_passwd[32];         /*!< password of station interface */
    char softap_ssid[32];        /*!< SSID of softap interface */
    char softap_passwd[32];      /*!< password of station interface */
    uint8_t softap_authmode;        /*!< authentication mode of softap interface */
    bool softap_authmode_set;       /*!< is authentication mode of softap interface set */
    uint8_t softap_max_conn_num;    /*!< max connection number of softap interface */
    bool softap_max_conn_num_set;   /*!< is max connection number of softap interface set */
    uint8_t softap_channel;         /*!< channel of softap interface */
    bool softap_channel_set;        /*!< is channel of softap interface set */
} blufi_extra_info_t;


// 关于 BLE 加密与协议协商
// {{{
void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free);
int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len);
int blufi_security_init(void);
void blufi_security_deinit(void);
// }}}

static void blufi_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);

static char blufi_ble_name[32] = "BLUFI_DEVICE";
static uint8_t blufi_service_uuid128[32] = {
    /* LSB <--------------------------------------------------------------------------------> MSB */
    //first uuid, 16bit, [12],[13] is the value
    0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
};

//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] =  {0x12, 0x23, 0x45, 0x56};
static esp_ble_adv_data_t blufi_adv_data = {
    .set_scan_rsp = FALSE,
    .include_name = TRUE,
    .include_txpower = TRUE,
    .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec
    .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec
    .appearance = 0x00,
    .manufacturer_len = 0,
    .p_manufacturer_data =  NULL,
    .service_data_len = 0,
    .p_service_data = NULL,
    .service_uuid_len = 16,
    .p_service_uuid = blufi_service_uuid128,
    .flag = 0x6,
};

static esp_ble_adv_params_t blufi_adv_params = {
    .adv_int_min        = 0x100,
    .adv_int_max        = 0x100,
    .adv_type           = ADV_TYPE_IND,
    .own_addr_type      = BLE_ADDR_TYPE_PUBLIC,
    //.peer_addr            =
    //.peer_addr_type       =
    .channel_map        = ADV_CHNL_ALL,
    .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};

#define WIFI_LIST_NUM   10

static wifi_config_t sta_config;
static wifi_config_t ap_config;

/* connect infor*/
static uint8_t server_if;
static uint16_t conn_id;

// 要上报的信息
static blufi_extra_info_t blufi_extra_info;

// blufi 配网启动时间
static uint32_t blufi_start_time = 0;


// 向 Blufi 手机 APP 上报 WiFi 信息
esp_err_t blufi_report_wifi_info() {
    wifi_mode_t mode;
    esp_blufi_extra_info_t info;

    esp_wifi_get_mode(&mode);

    memset(&info, 0, sizeof(esp_blufi_extra_info_t));

    if((mode == WIFI_MODE_STA) || (mode == WIFI_MODE_APSTA)) {
        wifi_ap_record_t ap_info;
        memset(&ap_info, 0, sizeof(ap_info));
        esp_wifi_sta_get_ap_info(&ap_info);

        memcpy(info.sta_bssid, ap_info.bssid, 6);
        info.sta_bssid_set = TRUE;
        info.sta_ssid = ap_info.ssid;
        info.sta_ssid_len = strlen((char*)ap_info.ssid);
    }
    if((mode == WIFI_MODE_AP) || (mode == WIFI_MODE_APSTA)) {
        info.softap_ssid                = (uint8_t*)blufi_extra_info.softap_ssid;
        info.softap_ssid_len            = strlen(blufi_extra_info.softap_ssid);
        info.softap_passwd              = (uint8_t*)blufi_extra_info.softap_passwd;
        info.softap_passwd_len          = strlen(blufi_extra_info.softap_passwd);
        info.softap_authmode            = blufi_extra_info.softap_authmode;
        info.softap_authmode_set        = blufi_extra_info.softap_authmode_set;
        info.softap_max_conn_num        = blufi_extra_info.softap_max_conn_num;
        info.softap_max_conn_num_set    = blufi_extra_info.softap_max_conn_num_set;
        info.softap_channel             = blufi_extra_info.softap_channel;
        info.softap_channel_set         = blufi_extra_info.softap_channel_set;
    }

    return esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info);
}

// 向 Blufi 手机 APP 上报 WiFi 信息
esp_err_t blufi_report_wifi_scan_list() {
    esp_err_t ret;
    uint16_t apCount = 0;
    esp_wifi_scan_get_ap_num(&apCount);
    if (apCount == 0) {
        logi("Nothing AP found");
        return ESP_OK;
    }
    wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount);
    if (!ap_list) {
        loge("malloc error, ap_list is NULL");
        return ESP_ERR_NO_MEM;
    }
    ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, ap_list));
    esp_blufi_ap_record_t * blufi_ap_list = (esp_blufi_ap_record_t *)malloc(apCount * sizeof(esp_blufi_ap_record_t));
    if (!blufi_ap_list) {
        if (ap_list) {
            free(ap_list);
        }
        loge("malloc error, blufi_ap_list is NULL");
        return ESP_ERR_NO_MEM;
    }

    for (int i = 0; i < apCount; ++i) {
        blufi_ap_list[i].rssi = ap_list[i].rssi;
        memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid));
    }

    ret = esp_blufi_send_wifi_list(apCount, blufi_ap_list);
    esp_wifi_scan_stop();
    free(ap_list);
    free(blufi_ap_list);

    return ret;
}

// 处理 blufi 模块自己关心的系统事件
static void blufi_handle_system_event(void *cookie, esp_event_base_t event_base, int32_t event_id, void *event_data) {
	// 如果未启动 blufi 配网，则不响应任何事件
	if(!blufi_start_time) { return; }

    if( (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) ||
        (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_START))
    {
        // 向 Blufi 手机 APP 上报 WiFi 信息
        blufi_report_wifi_info();
    }
    else if(event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
        // 向 Blufi 手机 APP 上报 WiFi 信息
        blufi_report_wifi_scan_list();
    }
}

static esp_blufi_callbacks_t blufi_callbacks = {
    .event_cb = blufi_event_callback,
    .negotiate_data_handler = (esp_blufi_negotiate_data_handler_t)blufi_dh_negotiate_data_handler,
    .encrypt_func = blufi_aes_encrypt,
    .decrypt_func = blufi_aes_decrypt,
    .checksum_func = blufi_crc_checksum,
};

static void blufi_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) {
    /* actually, should post to blufi_task handle the procedure,
     * now, as a blufi, we do it more simply */
    switch (event) {
    case ESP_BLUFI_EVENT_INIT_FINISH:
        logi("BLUFI init finish\n");
        esp_ble_gap_set_device_name(blufi_ble_name);
        esp_ble_gap_config_adv_data(&blufi_adv_data);
        break;
    case ESP_BLUFI_EVENT_DEINIT_FINISH:
        logi("BLUFI deinit finish\n");
        break;
    case ESP_BLUFI_EVENT_BLE_CONNECT:
        logi("BLUFI ble connect\n");
        server_if = param->connect.server_if;
        conn_id = param->connect.conn_id;
        esp_ble_gap_stop_advertising();
        blufi_security_init();
        break;
    case ESP_BLUFI_EVENT_BLE_DISCONNECT:
        logi("BLUFI ble disconnect\n");
        blufi_security_deinit();
        esp_ble_gap_start_advertising(&blufi_adv_params);
        break;
    case ESP_BLUFI_EVENT_SET_WIFI_OPMODE:
        logi("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode);
        ESP_ERROR_CHECK( esp_wifi_set_mode(param->wifi_mode.op_mode) );
        break;
    case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP:
        logi("BLUFI requset wifi connect to AP\n");
        // 如果已经连接到了 WiFi，则不会有事件回调；所以需要断开重连一下
        esp_wifi_disconnect();
        esp_wifi_connect();
        break;
    case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
        logi("BLUFI requset wifi disconnect from AP\n");
        esp_wifi_disconnect();
        break;
    case ESP_BLUFI_EVENT_REPORT_ERROR:
        loge("BLUFI report error, error code %d\n", param->report_error.state);
        esp_blufi_send_error_info(param->report_error.state);
        break;
    case ESP_BLUFI_EVENT_GET_WIFI_STATUS:
        logi("BLUFI get wifi status from AP\n");
        blufi_report_wifi_info();
        break;
    case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE:
        logi("blufi close a gatt connection");
        esp_blufi_close(server_if, conn_id);
        break;
    case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA:
        // TODO ESP_BLUFI_EVENT_DEAUTHENTICATE_STA
        break;
	case ESP_BLUFI_EVENT_RECV_STA_BSSID:
        memcpy(sta_config.sta.bssid, param->sta_bssid.bssid, 6);
        sta_config.sta.bssid_set = 1;
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        logi("Recv STA BSSID %s\n", sta_config.sta.ssid);
        break;
	case ESP_BLUFI_EVENT_RECV_STA_SSID:
        strncpy((char *)sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len);
        sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        logi("Recv STA SSID %s\n", sta_config.sta.ssid);
        break;
	case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
        strncpy((char *)sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len);
        sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
        esp_wifi_set_config(WIFI_IF_STA, &sta_config);
        logi("Recv STA PASSWORD %s\n", sta_config.sta.password);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID:
        strncpy((char *)ap_config.ap.ssid, (char *)param->softap_ssid.ssid, param->softap_ssid.ssid_len);
        ap_config.ap.ssid[param->softap_ssid.ssid_len] = '\0';
        ap_config.ap.ssid_len = param->softap_ssid.ssid_len;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        logi("Recv SOFTAP SSID %s, ssid len %d\n", ap_config.ap.ssid, ap_config.ap.ssid_len);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD:
        strncpy((char *)ap_config.ap.password, (char *)param->softap_passwd.passwd, param->softap_passwd.passwd_len);
        ap_config.ap.password[param->softap_passwd.passwd_len] = '\0';
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        logi("Recv SOFTAP PASSWORD %s len = %d\n", ap_config.ap.password, param->softap_passwd.passwd_len);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM:
        logi("Recv SOFTAP MAX CONN NUM %d\n", param->softap_max_conn_num.max_conn_num);
        if (param->softap_max_conn_num.max_conn_num > 4) {
            loge("The SoftAP can't have max_conn_num > 4(%d)", param->softap_max_conn_num.max_conn_num);
            return;
        }
        ap_config.ap.max_connection = param->softap_max_conn_num.max_conn_num;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE:
        logi("Recv SOFTAP AUTH MODE %d\n", param->softap_auth_mode.auth_mode);
        if (param->softap_auth_mode.auth_mode >= WIFI_AUTH_MAX) {
            loge("Invalid SoftAP auth_mode: %d", param->softap_auth_mode.auth_mode);
            return;
        }
        ap_config.ap.authmode = param->softap_auth_mode.auth_mode;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        break;
	case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL:
        logi("Recv SOFTAP CHANNEL %d\n", param->softap_channel.channel);
        if (param->softap_channel.channel > 13) {
            loge("Invalid SoftAP channel: %d", param->softap_channel.channel);
            return;
        }
        ap_config.ap.channel = param->softap_channel.channel;
        esp_wifi_set_config(WIFI_IF_AP, &ap_config);
        break;
    case ESP_BLUFI_EVENT_GET_WIFI_LIST: {
        wifi_scan_config_t scanConf = {
            .ssid = NULL,
            .bssid = NULL,
            .channel = 0,
            .show_hidden = FALSE
        };
        logi("Scan WiFi list start ...");
        ESP_ERROR_CHECK(esp_wifi_scan_start(&scanConf, TRUE));
        break;
    }
    case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA:
        logi("Recv Custom Data %d\n", param->custom_data.data_len);
        esp_log_buffer_hex("Custom Data", param->custom_data.data, param->custom_data.data_len);
        break;
	case ESP_BLUFI_EVENT_RECV_USERNAME:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CA_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CLIENT_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_SERVER_CERT:
        /* Not handle currently */
        break;
	case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY:
        /* Not handle currently */
        break;;
	case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY:
        /* Not handle currently */
        break;
    default:
        break;
    }
}

static void blufi_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
    switch (event) {
    case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
        esp_ble_gap_start_advertising(&blufi_adv_params);
        break;
    default:
        break;
    }
}

void blufi_stop() {
	blufi_start_time = 0;
	esp_blufi_profile_deinit();
}

esp_err_t blufi_start(const char *ble_name) {
	// 如果已经启动，则要先停止之前的
	if(blufi_start_time) { blufi_stop(); }
	blufi_start_time = osGetMs();

	strncpy(blufi_ble_name, ble_name, sizeof(blufi_ble_name));
    logi("BLUFI VERSION %04x\n", esp_blufi_get_version());

    RET_ON_ERROR(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, blufi_handle_system_event, NULL));
    RET_ON_ERROR(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, blufi_handle_system_event, NULL));

    RET_ON_ERROR(esp_ble_gap_register_callback(blufi_gap_event_handler));
    RET_ON_ERROR(esp_blufi_register_callbacks(&blufi_callbacks));
    RET_ON_ERROR(esp_blufi_profile_init());

    return ESP_OK;
}
